#include "stdafx.h"

#include <map>
#include <boost/thread/mutex.hpp>
#include <boost/thread.hpp>
#include <vector>
#include <string>
#include <fstream>

#include "DataInterface.h"
#include "DataInterfaceTypeInfo_i.c"

#include "COpcXmlElement.h"
#include "COpcString.h"
#include "OpcDaHelpers.h"
#include "opcda.h"

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/lexical_cast.hpp>



using namespace boost::algorithm;

using std::map;
using std::vector;
using std::wstring;
using std::wofstream;
using std::wifstream;
using boost::lexical_cast;
using boost::shared_mutex;

extern UINT g_uRefs; // count of OPC-server instances


map<unsigned, tag_state_sync_t> g_tag_values; 
shared_mutex g_tag_values_mutex;

unsigned data_interface_count = 0;

DataInterface::DataInterface() 
	: m_refCount(0)
{

	COpcString cFileName = OpcDaGetModulePath() + _T("\\") + 
		OpcDaGetModuleName() + _T(".cfg");

	wifstream cfg(const_cast<COpcString &>(cFileName).GetBuffer());
	if (!cfg.is_open())
		return ;
	
	g_tag_values_mutex.lock();
	while (!cfg.eof()) {
		vector<wchar_t> data(1000);
		cfg.getline(&data.front(), data.size() - 1);
		wstring buffer(&data.front());
		if (buffer.empty())
			continue;
		tag_config_t tag_config = parse_tag_config_string(buffer);
				
		tag_state_sync_t& tag = g_tag_values[tag_config.tag_id];
		tag.opc_access = tag_config.opc_access;
		tag.quality = OPC_QUALITY_BAD;
		tag.value.vt = VT_I4;
		tag.value.intVal = 0;
		VariantChangeType(&tag.value, &tag.value, 0, tag_config.vt);
		CoFileTimeNow(&tag.timestamp);
	}

	g_tag_values_mutex.unlock();

}

DataInterface::~DataInterface() {
	g_tag_values_mutex.lock();
	g_tag_values.clear();
	g_tag_values_mutex.unlock();
	--data_interface_count;

	if (g_uRefs == 0)
		COpcComModule::ExitProcess(S_OK);

}

STDMETHODIMP DataInterface::CreateInstance(IUnknown** ppv, const CLSID* clsid) {
	if (data_interface_count)
		return E_FAIL;
	if (ppv == NULL) 
		return E_POINTER;
	try {
		*ppv = NULL;
		DataInterface* pObject = new DataInterface();
		++data_interface_count;
	    return pObject->QueryInterface(IID_IUnknown, (void**)ppv);
	}
	catch (...) {
		return S_FALSE;
	}
}


unsigned DataInterface::instance_count() {
	return data_interface_count;
}

HRESULT STDMETHODCALLTYPE DataInterface::AddTag(
    /* [in] */ tag_config_t configuration,
    /* [in]  */ BOOL last_tag
    ) 
{
    static vector<tag_config_t> config;
    static vector<wstring> tag_names;

    tag_names.push_back(configuration.opc_path);

    configuration.opc_path = &tag_names.back()[0];
    config.push_back(configuration);

    HRESULT result = S_OK;
    if (last_tag) {
        result = Configure(config.size(), &config.front());
        config.clear();
        tag_names.clear();
    }

    return result;
}

HRESULT STDMETHODCALLTYPE DataInterface::Configure( 
    /* [in] */ long configuration_size,
	/* [size_is][in] */ tag_config_t configuration[  ]
	)
{
	g_tag_values_mutex.lock(); // get exclusive access for data structure

	// write cfg for OPC server
	COpcString cFileName;
	cFileName = OpcDaGetModulePath() + _T("\\") + OpcDaGetModuleName() + _T(".cfg");
	wofstream cfg(cFileName.GetBuffer());

	for (unsigned tag_index = 0; tag_index < configuration_size; ++tag_index) {
		const tag_config_t& tag_config = configuration[tag_index];
		
		cfg << tag_config.tag_id << cfg.widen('\t');
		cfg << tag_config.vt << cfg.widen('\t');
		cfg << tag_config.opc_access << cfg.widen('\t');
		cfg << 100 << cfg.widen('\t'); // 100 ms scan rate, update period from data structure to OPC
		cfg << tag_config.opc_path;
		cfg << std::endl;
	}

	// prepare data structure
	g_tag_values.clear();
	for (unsigned tag_index = 0; tag_index < configuration_size; ++tag_index) {
		const tag_config_t& tag_config = configuration[tag_index];
		tag_state_sync_t& tag_state = g_tag_values[tag_config.tag_id];

		tag_state.value.vt = VT_I4; tag_state.value.intVal = 0;
		VariantChangeType(&tag_state.value, &tag_state.value, 0, tag_config.vt);

		tag_state.opc_access = tag_config.opc_access;
		tag_state.quality = OPC_QUALITY_BAD;
		CoFileTimeNow(&tag_state.timestamp);
	}
	g_tag_values_mutex.unlock();

	return S_OK;
}

HRESULT STDMETHODCALLTYPE DataInterface::Write( 
    /* [in] */ long value_count,
    /* [size_is][in] */ tag_value_t values[  ])
{
	g_tag_values_mutex.lock_shared();
	
	HRESULT result = S_OK;
	for (unsigned index = 0; index < value_count; ++index) {
		// lock g_tag_values write, but allow for read
		// single writer, multiple readers
		map<unsigned, tag_state_sync_t>::iterator tag_state_iterator = 
			g_tag_values.find(values[index].tag_id);
		
		if (tag_state_iterator == g_tag_values.end()) {
			result = S_FALSE;
			continue;
		}
		tag_state_sync_t& tag_state = tag_state_iterator->second;
		 
		// allow writes only for OPC-readable items
		if (tag_state.opc_access == WriteOnly) {
			result = S_FALSE;
			continue;
		}
		tag_state.access_mutex.lock(); // exclusive access
		VariantCopy(/*dest*/&tag_state.value, /*src*/&values[index].value);
		CoFileTimeNow(&tag_state.timestamp); // what is current time? UTC?

		tag_state.quality = OPC_QUALITY_GOOD;
		tag_state.access_mutex.unlock();
	}

	g_tag_values_mutex.unlock_shared();
	return result;
}

HRESULT STDMETHODCALLTYPE DataInterface::Read( 
    /* [in] */ unsigned int value_count,
    /* [size_is][out][in] */ tag_value_t values[  ])
{
	g_tag_values_mutex.lock_shared();

	HRESULT result = S_OK;
	for (unsigned index = 0; index < value_count; ++index) {
		map<unsigned, tag_state_sync_t>::iterator tag_state_iterator = 
			g_tag_values.find(values[index].tag_id);
		if (tag_state_iterator == g_tag_values.end()) {
			result = S_FALSE;
			continue;
		}
		tag_state_iterator->second.access_mutex.lock_shared(); // shared access
		VariantCopy(&values[index].value, &tag_state_iterator->second.value);
		tag_state_iterator->second.access_mutex.unlock_shared(); 
	}
	
	g_tag_values_mutex.unlock_shared();
	return S_OK;
}


// IUnknown
STDMETHODIMP DataInterface::QueryInterface(REFIID riid, void** pIFace)
{
	// Which aspect of me do they want?
	if(riid == IID_IUnknown)
	{
		*pIFace = (IUnknown*)(IDataInterface *)this;
		// MessageBox(NULL, "Handed out IUnknown","QI",MB_OK | MB_SETFOREGROUND);
	}
	else if(riid == IID_IDataInterface)
	{
		*pIFace = (IDataInterface *)this;
		//MessageBox(NULL, L"Handed out IMyOPCCacheLink",L"QI",MB_OK | MB_SETFOREGROUND);
	}
	else
	{
		*pIFace = NULL;
		return E_NOINTERFACE;
	}

	((IUnknown*)(*pIFace))->AddRef();
	return S_OK;
}

STDMETHODIMP_(DWORD) DataInterface::AddRef()
{
	++m_refCount;
	return m_refCount;
}

STDMETHODIMP_(DWORD) DataInterface::Release()
{
	if(--m_refCount == 0)
	{
		delete this;
		return 0;
	}
	else
		return m_refCount;
}


